/** @file    SocketUdpQt.cpp
 *  @author  Alessandro Polo
 *  @version $Id: SocketUdpQt.cpp 2851 2010-08-06 19:37:23Z alex $
 *  @brief
 * File containing methods for the wosh::network::qt::SocketUdpQt class.
 * The header for this class can be found in SocketUdpQt.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include <framework/network/qt/SocketUdpQt.h>
 #include <core/ObjectAllocator.h>
 #include <framework/network/NetworkUtilities.h>
 #include <core/Thread.h>


#ifdef _SocketUdp_DEBUG_ENABLED
# define LOG( LEVEL, FORMAT, ... ) Log(LEVEL, FORMAT, __VA_ARGS__);
#else
# define LOG( LEVEL, FORMAT, ... ) ;
#endif


using namespace std;
namespace wosh {
 namespace network {
  namespace qt {

  WOSH_REGISTER(wosh::network::qt::SocketUdpQt, "wosh::network::SocketUdp", 1.0, _static_SocketUdpQt )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

SocketUdpQt::SocketUdpQt( )
	: QThread(), wosh::network::SocketUdp() {
	this->udpSocket = NULL;
	this->ready = false;
	this->failure = false;
 }

SocketUdpQt::~SocketUdpQt() {
	if ( this->isRunning() )
		unbindSocket();
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// BINDING

WRESULT SocketUdpQt::bindSocket( unsigned long timeout )
 {
	if ( this->ready ) {
		LOG(LOG_CRITICAL, ":bindSocket() Already Binded!" );
		return WRET_ERR_WRONG_STATE;
	 }
	this->ready = false;
	this->failure = false;
	start();
	if ( timeout == 0 ) {
		LOG(LOG_INFO, ":bindSocket() Asynch Binding.." );
		return WRET_OK;
	 }
	timeout = (unsigned long)(timeout / 10) + 1;
	unsigned long i = 0;
	while( ++i < timeout ) {
		if ( this->ready )
			return WRET_OK;
		if ( this->failure ) {
			LOG(LOG_CRITICAL, ":bindSocket() FAILURE Detected!" );
			break;
		 }
		ThreadImpl::sleepForMSec(10);
	 }
	if ( !this->failure ) {
		LOG(LOG_CRITICAL, ":bindSocket() TIMED-OUT!" );
	 }

	return WRET_ERR_INTERNAL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT SocketUdpQt::unbindSocket( unsigned long timeout )
 {
	if ( !this->ready ) {
		LOG(LOG_WARNING, ":unbindSocket() Not Binded!" );
		return WRET_ERR_WRONG_STATE;
	 }
	this->ready = false;
	LOG(LOG_VERBOSE, ":unbindSocket() UnBinding.." );
	QThread::quit();
	if ( timeout == 0 ) {
		LOG(LOG_INFO, ":bindSocket() Asynch Binding.." );
		return WRET_OK;
	 }
	QThread::wait(timeout);
	if ( this->isRunning() ) {
		LOG(LOG_CRITICAL, ":unbindSocket() Not Binded!" );
		return WRET_ERR_INTERNAL;
	 }
	LOG(LOG_INFO, ":unbindSocket() UnBinded." );
	return WRET_OK;
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// BINDING
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN

void SocketUdpQt::run()
 {
	LOG(LOG_VERBOSE, ":run() Allocating Internal Socket.." );
	this->udpSocket = new QUdpSocket();

	QHostAddress addr;
	addr.setAddress( this->address.c_str() );

	LOG(LOG_INFO, ":run() Binding Socket to '%s' at port %d..", this->address.c_str(), this->port );
#ifdef _OS_POSIX
	bool binded = this->udpSocket->bind( addr, this->port, QUdpSocket::ShareAddress );
#else
	bool binded = this->udpSocket->bind( addr, this->port );
#endif
	if ( binded == false ) {
		this->failure = true;
		LOG(LOG_CRITICAL, ":run() FAILED : Binding Socket to '%s' at port %d.. [%s]", this->address.c_str(), this->port,  udpSocket->errorString().toAscii().data() );
		delete udpSocket;
		this->udpSocket = NULL;
	 }
	else {
		connect(this->udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()), Qt::DirectConnection);
		this->ready = true;
		this->exec();
		this->ready = false;
	 }

	if ( this->udpSocket != NULL )
		delete this->udpSocket; udpSocket = NULL;
 }

////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////// INCOMING DATAGRAMS

void SocketUdpQt::processPendingDatagrams()
 {
	if ( this->listener == NULL ) {
		udpSocket->readDatagram( NULL, 0 ); // ignore datagram
		LOG(LOG_VERBOSE, ":processPendingDatagrams() IGNORING Pending Datagram!" );
		return;
	 }

	QByteArray datagram;
	QHostAddress sender;
	quint16 senderPort;
	std::string senderAdd;
	datagram.resize( udpSocket->pendingDatagramSize() );

	udpSocket->readDatagram( datagram.data(), datagram.size(), &sender, &senderPort );

	senderAdd = std::string( sender.toString().toLatin1().data() );
	
	if ( this->ignoreSelf ) {
		if ( senderPort == this->port && senderAdd == this->address ) {
			LOG(LOG_VERBOSE, ":processPendingDatagrams() IGNORING Pending Datagram from '%s' : %d!", sender.toString().toAscii().data(), senderPort );
			return;
		 }
	 }

	senderAdd += ":" + wosh::Utilities::toString( (int)senderPort );

	this->listener->udpIncomingDatagram( datagram.data(), datagram.size(), senderAdd, this );
 }

////////////////////////////////////////////////////////////////////////////////////////// INCOMING DATAGRAMS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////// OUTGOING DATAGRAMS

int SocketUdpQt::sendDatagram( const std::string& destination, const char* data, int size )
 {
	int port = wosh::network::NetworkUtilities::getPortFromDestination(destination);
	if ( port <= 0 )
		return -1;
	QHostAddress addr;
	addr.setAddress( QString(wosh::network::NetworkUtilities::getAddressFromDestination(destination).c_str()) );

	if ( this->udpSocket == NULL ) {
		LOG(LOG_WARNING, ":processPendingDatagrams() [STATIC MODE] Datagram to '%s'", destination.c_str() );
		QUdpSocket tmp_socket;
		return tmp_socket.writeDatagram( data, size, addr, port );
	 }

	return this->udpSocket->writeDatagram( data, size, addr, port );
  }

////////////////////////////////////////////////////////////////////////////////////////// OUTGOING DATAGRAMS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

  }; // namespace qt
 }; // namespace network
}; // namespace wosh
